﻿using System;
using System.Collections.Generic;
using System.Text;
using Stauffware.Common.Core;
using NUnit.Framework;

namespace UnitTests.Core.Parsing
{

	public class EnumParseTests
	{
		//=============================================================================
		// Test Set #1 : a non-flags enum

		public enum TestEnum1 : sbyte
		{
			Sopranono,		// intentionally misspelled
			Soprano,
			Alto,
			Tenor,
			Baritone,
			Bass,
			Contrabass
		}

		[TestCase(	"",		EnumParseOptions.None,
					"",		null	)]

		[TestCase(	"",		EnumParseOptions.ParseEntireInput,
					"",		null	)]

		[TestCase(	"",		EnumParseOptions.AllowWhiteSpace,
					"",		null	)]

		[TestCase(	" ",	EnumParseOptions.None,
					"",		null	)]

		[TestCase(	" ",	EnumParseOptions.AllowWhiteSpace,
					" ",	null	)]

		[TestCase(	" ",	EnumParseOptions.ParseEntireInput,
					" ",	"invalid value"	)]

		[TestCase(	">",	EnumParseOptions.ParseEntireInput,
					">",	"invalid value")]

		[TestCase(	" ",	EnumParseOptions.AllowWhiteSpace | EnumParseOptions.ParseEntireInput,
					" ",	null	)]

		[TestCase(	" !",	EnumParseOptions.AllowWhiteSpace | EnumParseOptions.ParseEntireInput,
					" !",	"invalid value"	)]

		[TestCase("Alto", EnumParseOptions.None,
					"Alto",		TestEnum1.Alto	)]

		[TestCase(	" alto ",	EnumParseOptions.IgnoreCase | EnumParseOptions.AllowWhiteSpace,
					" alto ",	TestEnum1.Alto	)]

		[TestCase(	"bar",		EnumParseOptions.AllowAbbreviations | EnumParseOptions.IgnoreCase,
					"bar",		TestEnum1.Baritone	)]

		[TestCase(	"ba",		EnumParseOptions.AllowAbbreviations | EnumParseOptions.IgnoreCase | EnumParseOptions.ParseEntireInput,
					"ba",		"ambiguous abbreviation"	)]

		[TestCase(	"Soprano",	EnumParseOptions.AllowAbbreviations,
					"Soprano",	TestEnum1.Soprano	)]

		[TestCase(	"coloratura",	EnumParseOptions.AllowAbbreviations | EnumParseOptions.ParseEntireInput,
					"coloratura",	"invalid value"	)]

		[TestCase(	"Alto schmalto",	EnumParseOptions.None,
					"Alto",				TestEnum1.Alto	)]

		[TestCase(	"Alto schmalto",	EnumParseOptions.ParseEntireInput,
					"Alto ",			"extraneous input found after value")]

		public static void Test1 (
					string input,				// the input string
					EnumParseOptions options,	// parse options
					string xParsed,				// expected ParsedText
					object xResult)				// Range value, error message string, or null
		{
			Test<TestEnum1>(input, options, xParsed, xResult);
#if false
			EnumParser<TestEnum1> parser = new EnumParser<TestEnum1>(options);
			EnumParse<TestEnum1> parse = parser.Parse(input);

			if (xResult is TestEnum1)
			{
				NUnit.Framework.Assert.True(parse.GotValue, "GotValue");
				NUnit.Framework.Assert.False(parse.IsError, "IsError");
				NUnit.Framework.Assert.False(parse.IsEmpty, "IsEmpty");
				NUnit.Framework.Assert.AreEqual((TestEnum1) xResult, parse.Value, "Value");
			}
			else if (xResult is string)
			{
				NUnit.Framework.Assert.False(parse.GotValue, "GotValue");
				NUnit.Framework.Assert.True(parse.IsError, "IsError");
				NUnit.Framework.Assert.False(parse.IsEmpty, "IsEmpty");
				NUnit.Framework.Assert.AreEqual((string) xResult, parse.ErrorMessage, "ErrorMessage");
			}
			else if (xResult == null)
			{
				NUnit.Framework.Assert.False(parse.GotValue, "GotValue");
				NUnit.Framework.Assert.False(parse.IsError, "IsError");
				NUnit.Framework.Assert.True(parse.IsEmpty, "IsEmpty");
			}
			else Stauffware.Common.Core.Assert.NotReached("BOGUS TEST! xResult is a " + xResult.GetType().Name);

			NUnit.Framework.Assert.AreEqual(xParsed, parse.ParsedText, "ParsedText");
#endif
		}

		//=============================================================================
		// Test Set #2 : a Flags enum

		[Flags]
		public enum TestEnum2f
		{
			None = 0,
			Keyboard = 1,
			Percussion = 2,
			String = 4,
			Wind = 8
		}

		[TestCase("", EnumParseOptions.None,
					"", null)]

		[TestCase("None", EnumParseOptions.None,
					"None", TestEnum2f.None	)]

		[TestCase("Wind", EnumParseOptions.None,
					"Wind", TestEnum2f.Wind	)]

		[TestCase("Keyboard|Wind", EnumParseOptions.AllowMultipleValues | EnumParseOptions.ParseEntireInput,
					"Keyboard|Wind", TestEnum2f.Keyboard | TestEnum2f.Wind	)]

		[TestCase(" Wind + Keyboard ", EnumParseOptions.AllowMultipleValues | EnumParseOptions.ParseEntireInput | EnumParseOptions.AllowWhiteSpace,
					" Wind + Keyboard ", TestEnum2f.Keyboard | TestEnum2f.Wind	)]

		[TestCase("Wind+Keyboard|String", EnumParseOptions.AllowMultipleValues | EnumParseOptions.ParseEntireInput,
					"Wind+Keyboard|", "inconsistent value separators" )]

		public static void Test2 (
					string input,				// the input string
					EnumParseOptions options,	// parse options
					string xParsed,				// expected ParsedText
					object xResult)				// Range value, error message string, or null
		{
			Test<TestEnum2f>(input, options, xParsed, xResult);
		}

		//=============================================================================
		// generic code

		protected static void Test<T> (
					string input,				// the input string
					EnumParseOptions options,	// parse options
					string xParsed,				// expected ParsedText
					object xResult)				// Range value, error message string, or null
			where T : struct
		{
			EnumParser<T> parser = new EnumParser<T>(options);
			EnumParse<T> parse = parser.Parse(input);

			Validation val = new Validation();
			val.AddInfoLine("actual parse = " + parse.ToString());

			val.CheckEqual(xResult is T, parse.GotValue, "GotValue");
			val.CheckEqual(xResult is string, parse.IsError, "IsError");
			val.CheckEqual(xResult == null, parse.IsEmpty, "IsEmpty");
			val.CheckEqual(xParsed, parse.ParsedText, "ParsedText");

			if (parse.GotValue)
				if (xResult is T)
					val.CheckEqual((T) xResult, parse.Value, "Value");
				else val.CheckEqual(null, parse.Value, "Value");

			if (parse.IsError)
				if (xResult is string)
					val.CheckEqual((string) xResult, parse.ErrorMessage, "ErrorMessage");
				else val.CheckEqual(null, parse.ErrorMessage, "ErrorMessage");

			if (val.Failed)
				NUnit.Framework.Assert.Fail(val.ToString());
		}

	}

	//#################################################################################
	/// <summary>
	/// Bag for gathering up a bunch of errors before reporting them all at once.
	/// </summary>

	public class Validation
	{
		private ListString ListString = new ListString("\n");
		private int errCount;

		public bool Failed { get { return this.errCount > 0; } }

		public void CheckEqual (string expected, string actual, string descr=null)
		{
			if (expected != actual)
				this.AddError(ErrorUtils.ValueToString(expected), ErrorUtils.ValueToString(actual), descr);
		}

		public void CheckEqual (bool expected, bool actual, string descr=null)
		{
			if (expected != actual) 
				this.AddError(ErrorUtils.ValueToString(expected), ErrorUtils.ValueToString(actual), descr);
		}

		public void CheckEqual (object expected, object actual, string descr=null)
		{
			if (expected == null && actual == null)
				return;
			if (expected != null && actual != null && expected.Equals(actual))
				return;
			this.AddError(ErrorUtils.ValueToString(expected), ErrorUtils.ValueToString(actual), descr);
		}

		private void AddError (string xStr, string aStr, string descr)
		{
			this.errCount++;
			if (string.IsNullOrEmpty(descr))
				this.ListString.Add("\texpected " + xStr + "; got " + aStr);
			else this.ListString.Add("\t" + descr + ": expected " + xStr + "; got " + aStr);
		}

		/// <summary>
		/// Appends a line of information, but does not affect the error count.
		/// </summary>
		/// <param name="str"></param>
		public void AddInfoLine (string str)
		{
			this.ListString.Add("\t" + str);
		}

		public override string ToString ( )
		{
			return this.ListString.ToString();
		}
	}

}
